#!/usr/bin/perl -w
use strict;
use File::Find;
use File::Basename;
use File::Copy;
use File::Path;
use File::stat;
use Fcntl ':mode';

my ($current_buildroot_basedir, $menuconfig_file_name) = @ARGV;

my ($current_project_basedir, $current_uclibc_basedir, $current_filesystem_basedir, $current_busybox_basedir, $current_kernel_basedir);

my ($project_name, 
	$operation, 
	$old_project_name, 
	$cmp_project_name, 
	$include_file_system,
	$scripts_only) = ReadMenuConfig($menuconfig_file_name);

print "Processing $project_name operation=\"$operation\" $current_buildroot_basedir\n";
my $project_config_file_basename = qq(project.cfg);
my %config_param =
(
    "KERNEL_CONFIG" => "kernel.config",
    "BUILDROOT_CONFIG" => "buildroot.config",
    "BUSYBOX_CONFIG" => "busybox.config",
    "UCLIBC_CONFIG" => "uclibc.config",
    "FILESYSTEM_IMAGE" => "filesystem.tar.bz2"
);
my $project_located = 0;

my $current_kernel_config = $current_buildroot_basedir . "/" . $current_project_basedir . "/" . $current_kernel_basedir . "/.config";
my $current_buildroot_config = $current_buildroot_basedir . "/.config";
my $current_busybox_config = $current_buildroot_basedir . "/" . $current_project_basedir . "/" . $current_busybox_basedir . "/.config";
my $current_uclibc_config = $current_buildroot_basedir . "/" . $current_uclibc_basedir . "/.config";
my $current_file_system_path = $current_buildroot_basedir . "/" . $current_filesystem_basedir;

$current_file_system_path	=~ s|//|/|g;
$current_buildroot_config	=~ s|//|/|g;
$current_kernel_config		=~ s|//|/|g;
$current_busybox_config		=~ s|//|/|g;
$current_uclibc_config		=~ s|//|/|g;

print "current_buildroot_basedir = $current_buildroot_basedir\n";
print "current_busybox_basedir = $current_busybox_basedir\n";
print "current_kernel_basedir = $current_kernel_basedir\n";
print "current_uclibc_basedir = $current_uclibc_basedir\n";
print "current_filesystem_basedir = $current_filesystem_basedir\n";

# print "current_buildroot_config = $current_buildroot_config\n";
# print "current_busybox_config = $current_busybox_config\n";
# print "current_kernel_config = $current_kernel_config\n";
# print "current_uclibc_config = $current_uclibc_config\n";

print "Input arguments:\n";
print "\tbuild_basedir=$current_buildroot_basedir\n\tcurrent_buildroot_config=$current_buildroot_config\n";
print "\tcurrent_kernel_config=$current_kernel_config\n\tcurrent_busybox_config=$current_busybox_config\n";
print "\tcurrent_uclibc_config=$current_uclibc_config\n\tmenuconfig_file_name=$menuconfig_file_name\n";

# ======================================================================================
# ForceCopyFile(src, dest)
# Attempt to copy a file even if destination exists and is read-only
# ======================================================================================
sub ForceCopyFile
{
	my ($src, $dst) = @_;
	my ($retval, $fstat) = undef;
	if ( -f $dst )
	{
		$fstat = stat($dst);
		chmod $fstat->mode|S_IRWXU, $dst;
	}
	
	# actual copying
	$retval = copy($src, $dst);
	print "Copying $src to $dst...\n";
	# restore attributes	
	chmod $fstat->mode, $dst if defined($fstat);
	
	return $retval;
}

# ======================================================================================
# ReadMenuConfig(file_name)
# parse .config file to determine requested operation and parameters
# file_name - name of configuration file generated by mconf
# ======================================================================================
sub ReadMenuConfig
{
    my ($name, $op, $new_name, $old_name, $cmp_name, $include_filesystem, $scripts_only_option);
    my $file_name = shift;
    open MENU_CONFIG, "< $file_name";
    $name = undef;
    $include_filesystem = 0;
    while (<MENU_CONFIG>)
    {
		$current_project_basedir = $1 if (/^BASEDIR_PROJECT=\"(.+)\"/);
		$current_buildroot_basedir = $1 if (/^BASEDIR_BUILDROOT=\"(.+)\"/);
		$current_kernel_basedir = $1 if (/^BASEDIR_KERNEL=\"(.+)\"/);
		$current_busybox_basedir = $1 if (/^BASEDIR_BUSYBOX=\"(.+)\"/);
		$current_uclibc_basedir = $1 if (/^BASEDIR_UCLIBC=\"(.+)\"/);
		$current_filesystem_basedir = $1 if (/^BASEDIR_FILESYSTEM=\"(.+)\"/);
        $name = $1 if (/(PROJECT_.+)=y/) && !defined($name);
        $cmp_name = $1 if (/(PROJECT_CMP_.+)=y/);
        $op = $1 if (/(OPERATION_.+)=y/);
        $new_name = $1 if (/NEW_PROJECT_NAME=\"(.+)\"/);
        $include_filesystem = 1 if (/INCLUDE_FILE_SYSTEM=y/);
        $scripts_only_option = 1 if (/INCLUDE_FILE_SYSTEM_SCRIPTS=y/);
	}
    close MENU_CONFIG;
	if ($op =~ /RENAME/)
	{
		$old_name = $name;
	}
    $name = $new_name if defined($new_name);
    chomp $name if defined($name);
    chomp $op if defined($op);
    if (defined($cmp_name))
    {
		chomp $cmp_name;
		$cmp_name =~ s/PROJECT_CMP_/PROJECT_/;
	}
	chomp $old_name if defined($old_name);
    return ($name, $op, $old_name, $cmp_name, $include_filesystem, $scripts_only_option);
}

# ======================================================================================
# LoadConfigurationFile(type, dest_file_name)
# Restores configuration of given type from project folder to specified location
# ======================================================================================
sub LoadConfigurationFile
{
    my ($param_key, $dst_file_name) = @_;
    
    if (exists $config_param{$param_key})
    {
        my $param = $config_param{$param_key};
        # BASE_DIR hash entry was created while scanning configuration file
        my $src_file_name = $config_param{"BASE_DIR"} . "/" . $param;
        CopyConfigurationFile($src_file_name, $dst_file_name);
        # copy to default configuration as well
        CopyConfigurationFile($src_file_name, "default/$param");
    }
    else
    {
        print "No parameter $param_key found in project file\n";
    }
}

# ======================================================================================
# CopyConfigurationFile(src_file, dst_file)
# Copies file and makes sure RWX are allowed for user
# ======================================================================================
sub CopyConfigurationFile()
{
	my ($src_file_name, $dst_file_name) = @_;
#   print "CopyConfigurationFile($src_file_name, $dst_file_name)\n";
    if (ForceCopyFile($src_file_name, $dst_file_name))
    {
#        print "Copied $src_file_name to $dst_file_name\n";
		my $fstat = stat($dst_file_name);
		chmod $fstat->mode|S_IRWXU, $dst_file_name if (defined($fstat));
    }
    else
        { print "Cannot copy $src_file_name to $dst_file_name\n"; }
}

# ======================================================================================
# SaveConfigurationFile(type, src_file_name)
# Saves configuration of given type from specified location to project folder
# ======================================================================================
sub SaveConfigurationFile
{
    my ($param_key, $src_file_name) = @_;
#    print "SaveConfigurationFile($param_key, $src_file_name)\n";
    if (exists $config_param{$param_key})
    {
        my $param = $config_param{$param_key};
#		print "param=$param, base=" . $config_param{"BASE_DIR"} . "\n";
        my $dst_file_name = $config_param{"BASE_DIR"} . "/" . $param;
        if (ForceCopyFile($src_file_name, $dst_file_name))
            { 
#            print "Copied $src_file_name to $dst_file_name\n"; 
            }
        else
            { print "Cannot copy $src_file_name to $dst_file_name\n"; }
    }
    else
    {
        print "No parameter $param_key found in project file\n";
    }
}

# ======================================================================================
# ScanProjectFile(file_name)
# Callback function invoked by find() on every file
# Looks for file which name matches pattern stored in $project_config_file_basename
# (by default "project.cfg")
# Function receives file name as its parameter ($_)
# ======================================================================================
sub ScanProjectFile
{
	# filter out .svn trees
    my $current_dir = $File::Find::dir;
	return if $current_dir =~ /\.svn/;
    if (!$project_located && /^$project_config_file_basename$/x)
    {
        print "Checking $File::Find::name for $project_name\n";
        open PROJECT_FILE_HANDLE, "< $_" or next;
        while (<PROJECT_FILE_HANDLE>)
        {
			# check if this is the project we are looking for
			# remove \n
			chomp;
			# remove \r just in case
			s/\r//;
            if (/^$project_name$/x)
            {
                # save location of the found file in configuration hash
                $config_param{"BASE_DIR"} = $current_dir;
                # gotcha!
                $project_located = 1;
                print "BASE_DIR = $current_dir\n";
            }
            next unless $project_located;
            if (/^([A-Z0-9_]+)=(.+)/)
            {
                my $p_value = $2;
                chomp $p_value;
                $config_param{$1} = $p_value;
            }
        }
        
        return unless $project_located;
        
        while ( my ($key, $value) = each %config_param)
        {
            print "\t$key => $value\n";
        }
        
        close PROJECT_FILE_HANDLE;
#        print "found " . $config_param{"BASE_DIR"} . "\n";
   }
}

# ======================================================================================
# CreateConfiguration(name)
# Creates a new folder and saves default Kconfig and project.cfg
# ======================================================================================
sub CreateConfiguration
{
	my ($name) = @_;
	
    mkdir $name;
    
    my $project_formal_name = "PROJECT_" . uc $name;
    open KCONFIG_HANDLE, "> $name/Kconfig";
    print KCONFIG_HANDLE "config $project_formal_name\n";
    print KCONFIG_HANDLE "\tbool \"$name\"\n";
    print KCONFIG_HANDLE "\thelp\n";
    print KCONFIG_HANDLE "\t\tPlease change project description\n";
    close KCONFIG_HANDLE;
  
    open DOTPROJECT_HANDLE, "> $name/$project_config_file_basename";
    print DOTPROJECT_HANDLE "Project file for $name\n";
    print DOTPROJECT_HANDLE "$project_formal_name\n";
    print DOTPROJECT_HANDLE "KERNEL_CONFIG=" . $config_param{"KERNEL_CONFIG"} . "\n";
    print DOTPROJECT_HANDLE "BUILDROOT_CONFIG=" . $config_param{"BUILDROOT_CONFIG"} . "\n";
    print DOTPROJECT_HANDLE "BUSYBOX_CONFIG=" . $config_param{"BUSYBOX_CONFIG"} . "\n";
    print DOTPROJECT_HANDLE "UCLIBC_CONFIG=" . $config_param{"UCLIBC_CONFIG"} . "\n";
    close DOTPROJECT_HANDLE;
}

# ======================================================================================
# ======================================================================================
sub DeleteConfiguration
{
	my ($name) = @_;
	rmtree($name);
}

# ======================================================================================
# ======================================================================================
sub CompareConfigurations
{
	my ($file_name1, $file_name2) = @_;
	my (%config1, %config2);
    open FILE1, "< $file_name1" or (print "Cannot open file 1 ($file_name1) for comparison\n" and return);
	while (<FILE1>)
	{
		if (/^([A-Z].+?)=(.+)/)
		{
			$config1{$1}=$2;
		}
	}
	close FILE1;
    open FILE2, "< $file_name2" or (print "Cannot open file 2 ($file_name2) for comparison\n" and return);
	while (<FILE2>)
	{
		if (/^([A-Z].+?)=(.+)/)
		{
			$config2{$1}=$2;
		}
	}
	close FILE2;
	
	print "$file_name1   <=>   $file_name2\n";
	my $cnt = 0;
	foreach my $k (sort keys %config1)
	{
		if (defined($config2{$k}))
		{
			unless ("$config1{$k}" eq "$config2{$k}")
			{
				print "$k: $config1{$k} <=> $config2{$k}\n";
				$cnt++;
			}
		}
		else
		{
			print "$k: $config1{$k} <=> UNDEFINED\n" ;
			$cnt++;
		}
	}
	foreach my $k2 (sort keys %config2)
	{
		unless (defined($config1{$k2}))
		{
			print "$k2: UNDEFINED <=> $config2{$k2}\n";
			$cnt++;
		}
	}
	print "Files are identical\n" if $cnt == 0;
}

sub exec_debug
{
#	print "(pseudo) "; 
	print @_; print "\n";
	exec @_;
}

# ======================================================================================
# RestoreFileSystem(key, path, scripts_only)
# Restores root file system from the tarball.
# Tarball name is extracted from configuration parameters hash
#	key - hash key
#	path - where to extract the file system to
#	scripts_only - restore /etc and /usr/local only
# ======================================================================================
sub RestoreFileSystem
{
	my ($key, $path, $scripts_only) = @_;
	my $fs_tarball = $config_param{"BASE_DIR"} . "/" . $config_param{$key};
	print "Restoring file system from $fs_tarball into $path ";
	if ($scripts_only)
	{
		print "(/etc and /usr/local only)\n";
		exec_debug("tar xjf $fs_tarball -C $path \"./etc/*\" \"./usr/local/*\" 2>/dev/null");
	}
	else
	{
		print "(all)\n";
		exec_debug("tar xjf $fs_tarball -C $path 2>/dev/null");
	}
}

# ======================================================================================
# SaveFileSystem(key, path, scripts_only)
# Saves root file system to a tarball.
# Tarball name is extracted from configuration parameters hash
#	key - hash key
#	path - where the file system is located
#	scripts_only - save /etc and /usr/local only
# ======================================================================================
sub SaveFileSystem
{
	my ($key, $path, $scripts_only) = @_;
	my $fs_tarball = $config_param{"BASE_DIR"} . "/" . $config_param{$key};
	unlink $fs_tarball;
	print "Saving file system from $path to $fs_tarball ";
	if ($scripts_only)
	{
		print "(/etc and /usr/local only)\n";
		exec_debug("tar cjf $fs_tarball --ignore-failed-read -C $path ./etc ./usr/local 2>/dev/null");
	}
	else
	{
		print "(all)\n";
		exec_debug("tar cjf $fs_tarball --ignore-failed-read -C $path ./ 2>/dev/null");
	}
}

if ($operation =~ /SELECT/)
{
    print "Operation <SELECT>\n";
    find (\&ScanProjectFile, ("."));
    
    die "Cannot locate project $project_name\n" unless $project_located;
    
    LoadConfigurationFile("KERNEL_CONFIG", $current_kernel_config);
    LoadConfigurationFile("BUILDROOT_CONFIG", $current_buildroot_config);
    LoadConfigurationFile("BUSYBOX_CONFIG", $current_busybox_config);
    LoadConfigurationFile("UCLIBC_CONFIG", $current_uclibc_config);
	RestoreFileSystem("FILESYSTEM_IMAGE", $current_file_system_path, $scripts_only) if ($include_file_system);
}
elsif ($operation =~ /SAVE_CURRENT_AS_NEW/)
{
    print "Operation <SAVE AS NEW>\n";
    
    CreateConfiguration($project_name);
    
    $config_param{"BASE_DIR"} = $project_name;
    
    SaveConfigurationFile("KERNEL_CONFIG", $current_kernel_config);
    SaveConfigurationFile("BUILDROOT_CONFIG", $current_buildroot_config);
    SaveConfigurationFile("BUSYBOX_CONFIG", $current_busybox_config);
    SaveConfigurationFile("UCLIBC_CONFIG", $current_uclibc_config);
	SaveFileSystem("FILESYSTEM_IMAGE", $current_file_system_path, $scripts_only) if ($include_file_system);
}
elsif ($operation =~ /UPDATE_FROM_CURRENT/)
{
    print "Operation <SAVE>\n";
    find (\&ScanProjectFile, ("."));
    die "Cannot locate project $project_name\n" unless $project_located;
    SaveConfigurationFile("KERNEL_CONFIG", $current_kernel_config);
    SaveConfigurationFile("BUILDROOT_CONFIG", $current_buildroot_config);
    SaveConfigurationFile("BUSYBOX_CONFIG", $current_busybox_config);
    SaveConfigurationFile("UCLIBC_CONFIG", $current_uclibc_config);
	SaveFileSystem("FILESYSTEM_IMAGE", $current_file_system_path, $scripts_only) if ($include_file_system);
}
elsif ($operation =~ /RENAME/)
{
    print "Operation <RENAME>\n";
    my $new_project_name = $project_name;
    $project_name = $old_project_name;
    find (\&ScanProjectFile, ("."));
    
    die "Cannot locate project $project_name\n" unless $project_located;

	my $old_project_folder = $config_param{"BASE_DIR"};
	my $new_project_folder = $new_project_name;
    CreateConfiguration($new_project_name);
    print "Copying from $old_project_folder to $new_project_folder\n";
    ForceCopyFile("$old_project_folder/".$config_param{"KERNEL_CONFIG"}, "$new_project_folder/".$config_param{"KERNEL_CONFIG"});
    ForceCopyFile("$old_project_folder/".$config_param{"BUILDROOT_CONFIG"}, "$new_project_folder/".$config_param{"BUILDROOT_CONFIG"});
    ForceCopyFile("$old_project_folder/".$config_param{"BUSYBOX_CONFIG"}, "$new_project_folder/".$config_param{"BUSYBOX_CONFIG"});
    ForceCopyFile("$old_project_folder/".$config_param{"UCLIBC_CONFIG"}, "$new_project_folder/".$config_param{"UCLIBC_CONFIG"});
    ForceCopyFile("$old_project_folder/".$config_param{"FILESYSTEM_IMAGE"}, "$new_project_folder/".$config_param{"FILESYSTEM_IMAGE"});
    DeleteConfiguration($old_project_folder);
}
elsif ($operation =~ /DELETE/)
{
    print "Operation <DELETE>\n";
    find (\&ScanProjectFile, ("."));
    die "Cannot locate project $project_name\n" unless $project_located;
    DeleteConfiguration($config_param{"BASE_DIR"});
}
elsif ($operation =~ /COMPARE/)
{
    print "Operation <COMPARE>\n"; # $cmp_project_name<->$project_name\n";
    my $project_name_1 = $project_name;
    $project_located = 0;
    find (\&ScanProjectFile, ("."));
    die "Cannot locate project $project_name\n" unless $project_located;
	my $project_1_folder = $config_param{"BASE_DIR"};
    $project_name = $cmp_project_name;
    $project_located = 0;
    find (\&ScanProjectFile, ("."));
    $project_name = $project_name_1;
    die "Cannot locate project $cmp_project_name\n" unless $project_located;
	my $project_2_folder = $config_param{"BASE_DIR"};
    CompareConfigurations("$project_1_folder/".$config_param{"KERNEL_CONFIG"}, "$project_2_folder/".$config_param{"KERNEL_CONFIG"});
    CompareConfigurations("$project_1_folder/".$config_param{"BUILDROOT_CONFIG"}, "$project_2_folder/".$config_param{"BUILDROOT_CONFIG"});
    CompareConfigurations("$project_1_folder/".$config_param{"BUSYBOX_CONFIG"}, "$project_2_folder/".$config_param{"BUSYBOX_CONFIG"});
    CompareConfigurations("$project_1_folder/".$config_param{"UCLIBC_CONFIG"}, "$project_2_folder/".$config_param{"UCLIBC_CONFIG"});
}
elsif ($operation =~ /VERIFY/)
{
    print "Operation <VERIFY>\n";
	print "current_buildroot_basedir=$current_buildroot_basedir: ";
	if ( ! -d $current_buildroot_basedir )
	{
		print "does NOT exist\n";
	}
	else
	{
		print "exists\n";
	}
	print "current_buildroot_config=$current_buildroot_config: ";
	if ( ! -f $current_buildroot_config )
	{
		print "does NOT exist\n";
	}
	else
	{
		print "exists\n";
	}
	print "current_kernel_config=$current_kernel_config: "; 
	if ( ! -f $current_kernel_config )
	{
		print "does NOT exist\n";
	}
	else
	{
		print "exists\n";
	}
	print "current_busybox_config=$current_busybox_config: "; 
	if ( ! -f $current_busybox_config )
	{
		print "does NOT exist\n";
	}
	else
	{
		print "exists\n";
	}
	print "menuconfig_file_name=$menuconfig_file_name: "; 
	if ( ! -f $menuconfig_file_name )
	{
		print "does not exist\n";
	}
	else
	{
		print "exists\n";
	}
	if (defined($project_name))
	{ 
		print "project_name=$project_name\n"; 
	}
	else
	{
		print "No project name provided\n";
	}
	
	if (defined($operation))
	{
		print "operation=$operation\n";
	}
	else
	{
		print "No operation specified\n";
	}
	if (defined($old_project_name))
	{
		print "old_project_name=$old_project_name\n";
	}
	else
	{
		print "No old project name specified\n";
	}
	if (defined($cmp_project_name))
	{
		print "cmp_project_name=$cmp_project_name\n" ;  
	}
	else
	{
		print "No project name to compare specified\n";
	}
	if (defined($include_file_system))
	{
		print "include_file_system=$include_file_system\n" ;  
	}
	else
	{
		print "File system inclusion not specified\n" ;  
	}
}
